#!/usr/bin/env python
#
# Copyright 2008 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import datetime
import logging

from google.appengine.ext import db
from google.appengine.api import users
from django.utils import simplejson


CURRENCIES = ["AUD", "USD"]
STATUS = ["PROPOSED", "REJECTED", "ACTIVE", "DELTED", "OBSOLETE"]

# PROPOSED -----.
#   |           |
#   |           V
#   V         REJECTED
# ACTIVE--------.
#   |           |
#   V           V
# DELETED     OBSOLETE

class Person(db.Model):
  user = db.UserProperty(required=True)

  def data(self):
    data = {}
    return data

  @staticmethod
  def GetPerson(user=None):
    user = user or users.get_current_user()
    person_list = Person.all().filter("user = ", user).fetch(limit=1)
    if not person_list:
      person = Person(user=user)
      person.put()
      return person
    return person_list[0]

  # Provided via back-reference
  # ---------------------------
  # group_set: groups person owns
  # groupmembership_set: groups a person is in
  # groupmembership_set: groups a person is in
  # transaction_set: txns that a person paid for
  # debts_to_pay:
  # monies_to_receive:

class Group(db.Model):
  name = db.StringProperty(required=True)
  owner = db.ReferenceProperty(Person, required=True)
  currency = db.StringProperty(choices=CURRENCIES, required=True)

  def data(self):
    data = {}
    data["name"] = self.name
    data["owner"] = self.owner.user.nickname()
    data["currency"] = self.currency
    members = [member.data() for member in self.groupmembership_set]
    data["groupmembership_set"] = members
    data["key"] = {"id": self.key().id()}
    return data

  def add_member(self, new_member, nickname):
    membership = GroupMembership(person=new_member, group=self, member_nickname=nickname)
    membership.put()
    # add a blank transaction for this user containing the balance for all other users
    Transaction.AddBlankTransaction(self.key().id(), new_member, nickname)
    return membership.key().id()
  
  @staticmethod
  def create_group(group, user_owner):
    group.put()
    group.add_member(user_owner, "Chris")
    return group.key()

class GroupMembership(db.Model):
  person = db.ReferenceProperty(Person, required=True)
  group = db.ReferenceProperty(Group, required=True)
  member_nickname = db.StringProperty()

  def data(self):
    data = {}
    data["nickname"] = self.member_nickname
    data["email"] = self.person.user.email()
    return data

  @staticmethod
  def GetMemberByNickname(nickname):
    logging.info("nickname=" + str(nickname))
    member = GroupMembership.all().filter("member_nickname =", nickname).get()
    logging.info("member=" + str(member))
    return member

class Transaction(db.Model):
  name = db.StringProperty(required=True)
  group = db.ReferenceProperty(Group, required=True)
  txn_date = db.DateProperty(required=True)
  txn_created = db.DateTimeProperty(auto_now_add=True)
  external_payee = db.StringProperty(required=False)
  payer = db.ReferenceProperty(Person, required=True)
  total_amount_micros = db.IntegerProperty()
  previous = db.SelfReferenceProperty(collection_name="next_set")
  next = db.SelfReferenceProperty(collection_name="previous_set")

  # The following is provided automatically via backref from the
  # TransactionDetail object.
  # transactiondetail_set = set of TransactionDetails for this transaction

  def AmountInDecimal(self):
    return "%.02f" % (self.total_amount_micros/1000000.0)

  def data(self):
    return {
       "name": self.name,
       "group": self.group.key().id(),
       "paid_to": self.external_payee,
       "paid_by": self.payer.user.email(),
       "amount": self.AmountInDecimal(),
       "number of details": self.transactiondetail_set.count()
        }
    
  def AsJson(self):
    obj_dict = self.data()
    return simplejson.dumps(obj_dict)

  def AddLine(self, participants, line, previous):
    logging.info("previous: " + str(previous.key().id()))
    for i in range(0, len(participants)):
      participant = participants[i]
      logging.info("participant: " + str(participant.person.user))
      delta = line[i]
      previous_details = TransactionDetail.all().filter("transaction = ",
                                                        previous)
      logging.info("previous_details: " + str(previous_details.count()))
      previous_detail = previous_details.filter("participant = ",
                                                participant.person).get()
      logging.info("previous_detail: " + str(previous_detail))
      before_amount_micros = previous_detail.after_amount_micros
      delta_amount_micros = int(delta)
      after_amount_micros = before_amount_micros + delta_amount_micros
      detail = TransactionDetail(transaction=self, participant=participant.person,
                                 before_amount_micros=before_amount_micros,
                                 delta_amount_micros=delta_amount_micros,
                                 after_amount_micros=after_amount_micros)
      detail.put()
  
  @staticmethod
  def GetMostRecentTransaction(group_id):
    return Transaction.all().order("-txn_created").get()

  @staticmethod
  def AddBlankTransaction(group_id, participant, nickname):
    name = nickname + " joins the group."
    group = Group.get_by_id(group_id)
    txn_date = datetime.date.today()
    payer = Person.GetPerson()
    blank_transaction = Transaction(name=name, group=group, txn_date=txn_date,
                                    payer=payer, total_amount_micros=0)
    blank_transaction.put()
    previous = Transaction. GetMostRecentTransaction(group_id)
    if previous is not None:
      for detail in previous.transactiondetail_set:
        existing_participant = detail.participant
        before_amount = detail.after_amount_macros
        new_detail = TransactionDetail(transaction=blank_transaction,
                                       participant=existing_participant,
                                       before_amount_micros=after_amount_micros,
                                       delta_amount_micros=0,
                                       after_amount_micros=after_amount_micros)
        new_detail.put()

    new_member_detail = TransactionDetail(transaction=blank_transaction,
                                          participant=participant,
                                          before_amount_micros=0,
                                          delta_amount_micros=0,
                                          after_amount_micros=0)
    new_member_detail.put()

class TransactionDetail(db.Model):
  transaction = db.ReferenceProperty(Transaction, required=True)
  participant = db.ReferenceProperty(Person,
      collection_name="debts_to_pay", required=True)
  before_amount_micros = db.IntegerProperty(required=True)
  delta_amount_micros = db.IntegerProperty(required=True)
  after_amount_micros = db.IntegerProperty(required=True)
